package javango.db;

import java.util.List;

import org.apache.commons.logging.LogFactory;

/**
 * Provides a framework for paginating a list of objects in a few lines of code.
 * 
 * @author johns
 *
 */
public class Paginator<T> {

	public static class Page<T> {
		Paginator<T> paginator;
		QuerySet<T> qs;
		int number;
		
		public Page(QuerySet<T> qs, int page, Paginator<T> paginator) {
			this.qs = qs;
			this.number = page;
			this.paginator = paginator;
		}

		public String toString() {
			return String.format("<Page %s of %s>", this.getNumber(), this.paginator.getNumPages());
		}
		
		/**
		 * Returns the object list for this Page
		 * @return
		 */
		public List<T> getObjectList() {
			try {
				return qs.list();
			} catch (ManagerException e) {
				//argg what should be done??
				// TODO
				LogFactory.getLog(Page.class).error(e,e);
			}
			return null;
		}
		
		/**
		 * Returns true if a next page exists
		 * @return
		 */
		public boolean getHasNext() {
			return this.getNumber() < this.paginator.getNumPages();
		}
		
		/**
		 * Returns true if a previous page exists
		 * @return
		 */
		public boolean getHasPrevious() {
			return this.getNumber() > 1;
		}
		
		/**
		 * Returns the total number of pages
		 * @return
		 */
		public int getNumber() {
			return number;
		}
		
		public Paginator<T> getPaginator() {
			return paginator;
		}
		
		public int getPreviousPageNumber() {
			return this.number - 1;			
		}
		
		public int getNextPageNumber() {
			return this.number + 1;
		}		
	}
	
	QuerySet<T> qs;
	int perPage;
	Integer numPages = null;
	
	boolean allowEmptyFirstPage = true; // TODO
	int orphans = 5; // TODO 
	
	public Paginator(QuerySet<T> qs, int perPage) {
		// TODO probably should require QuerySets to implement clone and use that here.
		this.qs = qs;
		this.perPage = perPage;
	}

	/**
	 * Validates the given 1-based page number.
	 * @param number
	 * @return
	 */
	public int validateNumber(int number) {
		if (number < 1) {
			throw new IllegalArgumentException("That page number is less than 1"); 
		} else if ( number > this.getNumPages() ) {
			if (number == 1 && this.allowEmptyFirstPage ) {
			} else {
				throw new IllegalArgumentException("That page contains no results");
			}
		}
		return number;
	}
	
    /**
     * Returns a Page object for the given 1-based page number."
     */
	public Page<T> getPage(int number) {
		try {
			number = this.validateNumber(number);
			int bottom = (number - 1) * this.perPage;
			int top = bottom + this.perPage;
			if (top + this.orphans >= this.getCount()) {
				top = this.getCount();
			}
			return new Page<T>(this.qs.limit(bottom,top), number, this);
		} catch (ManagerException e) {
			LogFactory.getLog(Paginator.class).error(e,e);
			return null;
		}
	}
	
	/**
	 * Returns the total number of pages.
	 * @return
	 */
	public int getNumPages() {
		if (this.numPages == null) {
	    	 if ((this.getCount() == 0) && (!this.allowEmptyFirstPage))  {
		    	 this.numPages = 0;
	    	 } else {
	    		 int hits = Math.max(1, this.getCount() - this.orphans);
	    		 this.numPages = (int)Math.ceil(hits / this.perPage);
			this.numPages++;
	    	 }
		}
		return this.numPages;
	}
	
	/**
	 * returns the total number of objects, across all pages.
	 * @return
	 */
	public int getCount() {
		try {
			return new Long(qs.count()).intValue();
		} catch (ManagerException e) {
			LogFactory.getLog(Paginator.class).error(e,e);
		}
		return 0;
	}
	
	public String toString() {
		StringBuilder b = new StringBuilder();
		b.append("paginate_by: " );
		b.append(perPage);
		b.append("; pages: ");
		b.append(getNumPages());
		b.append("; hits: ");
		b.append(getCount());
		return b.toString();
	}
}
